﻿using Sage.CloudStorage.Qiniu.Auth;
using Sage.CloudStorage.Qiniu.Events;
using Sage.CloudStorage.Qiniu.Models;
using Sage.CloudStorage.Qiniu.Utils;
using Sage.Data.Extensions;
using Sage.Http.Core;
using Sage.Http.Models;
using System.Collections.Concurrent;
using System.Text.Json;

namespace Sage.CloudStorage.Qiniu.Upload
{
    /// <summary>
    /// 七牛云分片上传服务
    /// 提供完整的分片上传功能，自动处理分片计算、上传策略应用、并发控制等
    /// </summary>
    /// <remarks>
    /// **核心功能**：
    /// - **智能分片**：根据文件大小自动计算最优分片策略
    /// - **策略应用**：支持所有类型的上传策略配置
    /// - **直接上传**：直接传入文件路径，内部处理所有细节
    /// - **并发控制**：可配置的并发数量，提高上传效率
    /// - **进度监控**：实时的上传进度回调和事件通知
    /// - **错误恢复**：上传失败时自动清理资源和重试机制
    /// - **断点续传**：支持中断后继续上传，提高大文件上传成功率
    /// 
    /// **使用场景**：
    /// - **大文件上传**：文件大小超过100MB的上传场景
    /// - **网络不稳定**：网络环境不稳定需要重试的场景
    /// - **进度监控**：需要显示上传进度和状态的场景
    /// - **批量上传**：多个文件同时上传的场景
    /// - **企业应用**：对上传可靠性要求较高的企业级应用
    /// 
    /// **架构设计**：
    /// - 完全异步设计，支持高并发操作
    /// - 事件驱动模式，解耦上传逻辑和业务处理
    /// - 可扩展架构，支持自定义策略和处理器
    /// - 内存优化，大文件上传不会占用过多内存
    /// </remarks>
    public class QiniuMultipartUploadService
    {
        #region 常量定义

        /// <summary>
        /// 默认分片大小（4MB）
        /// 经过测试的最佳分片大小，平衡了传输效率和稳定性
        /// </summary>
        public const int DefaultChunkSize = 4 * 1024 * 1024;

        /// <summary>
        /// 最小分片大小（1MB）
        /// 七牛云要求除最后一个分片外，其他分片必须>=1MB
        /// </summary>
        public const int MinChunkSize = 1 * 1024 * 1024;

        /// <summary>
        /// 最大分片大小（1GB）
        /// 七牛云分片大小上限
        /// </summary>
        public const long MaxChunkSize = 1024L * 1024 * 1024;

        /// <summary>
        /// 最大分片数量（10000）
        /// 七牛云支持的最大分片数量限制
        /// </summary>
        public const int MaxPartNumber = 10000;

        /// <summary>
        /// 建议使用分片上传的最小文件大小（10MB）
        /// 小于此大小的文件建议使用普通上传
        /// </summary>
        public const long RecommendedMinFileSize = 10 * 1024 * 1024;

        #endregion

        #region 私有字段

        /// <summary>
        /// HTTP请求管理器，负责所有网络请求的发送和管理
        /// </summary>
        private readonly HttpRequestManager _httpManager;

        /// <summary>
        /// 七牛云密钥信息，用于生成认证凭证
        /// </summary>
        private readonly QiniuKey _qiniuKey;

        /// <summary>
        /// 七牛云上传域名，根据存储区域选择
        /// </summary>
        private readonly string _uploadHost;

        #endregion

        #region 事件定义

        /// <summary>
        /// 上传进度变化事件
        /// </summary>
        public event EventHandler<UploadProgressEventArgs>? ProgressChanged;

        /// <summary>
        /// 上传完成事件
        /// </summary>
        public event EventHandler<UploadCompletedEventArgs>? UploadCompleted;

        #endregion

        #region 构造函数

        /// <summary>
        /// 初始化七牛云分片上传服务
        /// </summary>
        /// <param name="httpManager">HTTP请求管理器</param>
        /// <param name="qiniuKey">七牛云密钥</param>
        /// <param name="uploadHost">上传域名</param>
        public QiniuMultipartUploadService(
            HttpRequestManager httpManager,
            QiniuKey qiniuKey,
            string uploadHost = "https://up.qiniup.com")
        {
            _httpManager = httpManager ?? throw new ArgumentNullException(nameof(httpManager));
            _qiniuKey = qiniuKey ?? throw new ArgumentNullException(nameof(qiniuKey));

            if (!qiniuKey.IsValid())
                throw new ArgumentException("七牛云密钥无效", nameof(qiniuKey));

            _uploadHost = uploadHost ?? throw new ArgumentNullException(nameof(uploadHost));
        }

        #endregion

        #region 主要上传接口

        /// <summary>
        /// 上传文件（智能模式）
        /// </summary>
        public async Task<CompleteMultipartUploadResponse> UploadFileAsync(
            string filePath,
            string bucket,
            string key,
            UploadPolicy? uploadPolicy = null,
            UploadOptions? options = null,
            CancellationToken cancellationToken = default)
        {
            // 参数验证
            ArgumentException.ThrowIfNullOrEmpty(filePath, nameof(filePath));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            ArgumentException.ThrowIfNullOrEmpty(key, nameof(key));

            if (!File.Exists(filePath))
                throw new FileNotFoundException($"文件不存在: {filePath}");

            var fileInfo = new FileInfo(filePath);
            var fileName = fileInfo.Name;
            var fileSize = fileInfo.Length;
            var startTime = DateTime.Now;

            // 应用默认选项
            options ??= new UploadOptions();

            // 智能分片大小计算
            var chunkSize = options.ChunkSize ?? QiniuUtils.GetRecommendedChunkSize(fileSize);
            var maxConcurrency = Math.Max(options.MaxConcurrency, 1);

            // 验证分片参数
            var (isValid, message) = QiniuUtils.ValidateChunkParameters(fileSize, chunkSize);
            if (!isValid)
                throw new ArgumentException($"分片参数无效: {message}");

            // 自动计算分片数量
            var totalParts = CalculatePartCount(fileSize, chunkSize);

            if (options.EnableConsoleLog)
            {
                Console.WriteLine($"文件信息: {fileName}");
                Console.WriteLine($"大小: {QiniuUtils.FormatBytes(fileSize)}, 分片: {totalParts}个×{QiniuUtils.FormatBytes(chunkSize)}, 并发: {maxConcurrency}");
            }

            // 智能策略应用
            var effectivePolicy = uploadPolicy ?? CreateSmartUploadPolicy(bucket, key, fileName, fileSize);

            if (options.EnableConsoleLog)
            {
                Console.WriteLine($"上传策略: {effectivePolicy.Scope}");
                if (effectivePolicy.FsizeLimit.HasValue)
                    Console.WriteLine($"大小限制: {QiniuUtils.FormatBytes(effectivePolicy.FsizeLimit.Value)}");
                if (!string.IsNullOrEmpty(effectivePolicy.MimeLimit))
                    Console.WriteLine($"类型限制: {effectivePolicy.MimeLimit}");
            }

            try
            {
                CompleteMultipartUploadResponse result;

                // 根据文件大小选择上传方式
                if (fileSize <= RecommendedMinFileSize && options.ForceMultipart != true)
                {
                    if (options.EnableConsoleLog)
                        Console.WriteLine("使用普通上传（文件较小）");
                    result = await UploadSmallFileAsync(filePath, bucket, key, effectivePolicy, cancellationToken);
                }
                else
                {
                    if (options.EnableConsoleLog)
                        Console.WriteLine("使用分片上传");
                    result = await UploadLargeFileAsync(filePath, bucket, key, effectivePolicy, chunkSize, maxConcurrency, startTime, options, cancellationToken);
                }

                // 触发完成事件
                var duration = DateTime.Now - startTime;
                UploadCompleted?.Invoke(this, new UploadCompletedEventArgs
                {
                    IsSuccess = true,
                    Key = result.Key,
                    Hash = result.Hash,
                    FileSize = fileSize,
                    Duration = duration
                });

                if (options.EnableConsoleLog)
                    Console.WriteLine($"上传完成: {result.Key} ({QiniuUtils.FormatBytes(fileSize)}, 耗时: {duration.TotalSeconds:F1}秒)");

                return result;
            }
            catch (Exception ex)
            {
                // 触发失败事件
                UploadCompleted?.Invoke(this, new UploadCompletedEventArgs
                {
                    IsSuccess = false,
                    Key = key,
                    FileSize = fileSize,
                    Duration = DateTime.Now - startTime,
                    ErrorMessage = ex.Message,
                    Exception = ex
                });

                if (options.EnableConsoleLog)
                    Console.WriteLine($"上传失败: {ex.Message}");
                throw;
            }
        }

        /// <summary>
        /// 批量上传文件
        /// </summary>
        public async Task<List<BatchUploadResult>> UploadBatchAsync(
            IEnumerable<string> filePaths,
            string bucket,
            Func<string, string> keyGenerator,
            Func<string, UploadPolicy>? policyGenerator = null,
            UploadOptions? options = null,
            CancellationToken cancellationToken = default)
        {
            ArgumentNullException.ThrowIfNull(filePaths, nameof(filePaths));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            ArgumentNullException.ThrowIfNull(keyGenerator, nameof(keyGenerator));

            var files = filePaths.Where(File.Exists).ToList();
            var results = new ConcurrentBag<BatchUploadResult>();
            var batchOptions = options ?? new UploadOptions();
            var semaphore = new SemaphoreSlim(batchOptions.BatchConcurrency);

            if (batchOptions.EnableConsoleLog)
                Console.WriteLine($"开始批量上传: {files.Count} 个文件");

            var tasks = files.Select(async filePath =>
            {
                await semaphore.WaitAsync(cancellationToken);
                try
                {
                    var fileName = Path.GetFileName(filePath);
                    var key = keyGenerator(filePath);
                    var policy = policyGenerator?.Invoke(filePath);

                    var result = await UploadFileAsync(filePath, bucket, key, policy, batchOptions, cancellationToken);

                    results.Add(new BatchUploadResult
                    {
                        FilePath = filePath,
                        Key = key,
                        IsSuccess = true,
                        Result = result
                    });

                    if (batchOptions.EnableConsoleLog)
                        Console.WriteLine($"{fileName} 上传完成");
                }
                catch (Exception ex)
                {
                    results.Add(new BatchUploadResult
                    {
                        FilePath = filePath,
                        Key = keyGenerator(filePath),
                        IsSuccess = false,
                        Error = ex.Message,
                        Exception = ex
                    });

                    if (batchOptions.EnableConsoleLog)
                        Console.WriteLine($"{Path.GetFileName(filePath)} 上传失败: {ex.Message}");
                }
                finally
                {
                    semaphore.Release();
                }
            });

            await Task.WhenAll(tasks);

            var resultList = results.ToList();
            var successCount = resultList.Count(r => r.IsSuccess);

            if (batchOptions.EnableConsoleLog)
                Console.WriteLine($"批量上传完成: 成功 {successCount}/{resultList.Count}");

            return resultList;
        }

        /// <summary>
        /// 断点续传上传文件
        /// </summary>
        public async Task<CompleteMultipartUploadResponse> ResumeUploadAsync(
            string filePath,
            string bucket,
            string key,
            string? uploadId = null,
            UploadPolicy? uploadPolicy = null,
            UploadOptions? options = null,
            CancellationToken cancellationToken = default)
        {
            ArgumentException.ThrowIfNullOrEmpty(filePath, nameof(filePath));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            ArgumentException.ThrowIfNullOrEmpty(key, nameof(key));

            if (!File.Exists(filePath))
                throw new FileNotFoundException($"文件不存在: {filePath}");

            var fileInfo = new FileInfo(filePath);
            var fileSize = fileInfo.Length;
            var startTime = DateTime.Now;

            options ??= new UploadOptions();
            var chunkSize = options.ChunkSize ?? QiniuUtils.GetRecommendedChunkSize(fileSize);
            var totalParts = CalculatePartCount(fileSize, chunkSize);

            // 创建或使用现有的上传策略
            var effectivePolicy = uploadPolicy ?? CreateSmartUploadPolicy(bucket, key, fileInfo.Name, fileSize);
            var uploadAuth = new QiniuAuthProvider(_qiniuKey, effectivePolicy.ToJson());

            try
            {
                // 如果没有提供uploadId，初始化新的上传任务
                if (string.IsNullOrEmpty(uploadId))
                {
                    var initResponse = await InitiateMultipartUploadAsync(bucket, key, uploadAuth, cancellationToken);
                    uploadId = initResponse.UploadId;
                    if (options.EnableConsoleLog)
                        Console.WriteLine($"初始化新的上传任务: {uploadId}");
                }
                else
                {
                    if (options.EnableConsoleLog)
                        Console.WriteLine($"恢复上传任务: {uploadId}");
                }

                // 检查已上传的分片
                var existingParts = await GetAllExistingPartsAsync(bucket, key, uploadId, uploadAuth, cancellationToken);
                var existingPartNumbers = existingParts.Select(p => p.PartNumber).ToHashSet();

                if (options.EnableConsoleLog)
                    Console.WriteLine($"发现已上传分片: {existingParts.Count}/{totalParts}");

                if (existingParts.Count == totalParts)
                {
                    if (options.EnableConsoleLog)
                        Console.WriteLine("所有分片已上传完成，直接完成上传");

                    var completeRequest = new CompleteMultipartUploadRequest
                    {
                        Parts = [.. existingParts.Select(p => new CompletePartInfo
                        {
                            PartNumber = p.PartNumber,
                            Etag = p.Etag
                        }).OrderBy(p => p.PartNumber)],
                        Fname = fileInfo.Name,
                        MimeType = QiniuUtils.GetMimeType(filePath)
                    };

                    return await CompleteMultipartUploadAsync(bucket, key, uploadId, uploadAuth, completeRequest, cancellationToken);
                }

                // 上传缺失的分片
                var missingParts = Enumerable.Range(1, totalParts)
                    .Where(partNumber => !existingPartNumbers.Contains(partNumber))
                    .ToList();

                if (options.EnableConsoleLog)
                    Console.WriteLine($"需要上传 {missingParts.Count} 个缺失分片");

                // 上传缺失的分片
                var newParts = await UploadMissingPartsAsync(
                    filePath, bucket, key, uploadId, uploadAuth,
                    missingParts, chunkSize, options.MaxConcurrency,
                    startTime, options.ProgressCallback, options.EnableConsoleLog, cancellationToken);

                // 合并所有分片信息
                var allParts = existingParts.Select(p => new CompletePartInfo
                {
                    PartNumber = p.PartNumber,
                    Etag = p.Etag
                })
                    .Concat(newParts)
                    .OrderBy(p => p.PartNumber)
                    .ToList();

                // 完成上传
                var finalRequest = new CompleteMultipartUploadRequest
                {
                    Parts = allParts,
                    Fname = fileInfo.Name,
                    MimeType = QiniuUtils.GetMimeType(filePath)
                };

                var result = await CompleteMultipartUploadAsync(bucket, key, uploadId, uploadAuth, finalRequest, cancellationToken);

                var duration = DateTime.Now - startTime;
                if (options.EnableConsoleLog)
                    Console.WriteLine($"断点续传完成: {result.Key} (耗时: {duration.TotalSeconds:F1}秒)");

                return result;
            }
            catch (Exception ex)
            {
                if (!string.IsNullOrEmpty(uploadId))
                {
                    await TryAbortMultipartUploadAsync(bucket, key, uploadId, uploadAuth, options.EnableConsoleLog);
                }
                if (options.EnableConsoleLog)
                    Console.WriteLine($"断点续传失败: {ex.Message}");
                throw;
            }
        }

        #endregion

        #region 私有实现方法

        /// <summary>
        /// 上传小文件（普通上传模式）
        /// </summary>
        private async Task<CompleteMultipartUploadResponse> UploadSmallFileAsync(
            string filePath,
            string bucket,
            string key,
            UploadPolicy policy,
            CancellationToken cancellationToken)
        {
            // 小文件也使用分片上传，但只有一个分片
            return await UploadLargeFileAsync(filePath, bucket, key, policy, DefaultChunkSize, 1, DateTime.Now, new UploadOptions(), cancellationToken);
        }

        /// <summary>
        /// 上传大文件（分片上传模式）
        /// </summary>
        private async Task<CompleteMultipartUploadResponse> UploadLargeFileAsync(
            string filePath,
            string bucket,
            string key,
            UploadPolicy policy,
            int chunkSize,
            int maxConcurrency,
            DateTime startTime,
            UploadOptions options,
            CancellationToken cancellationToken)
        {
            using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            var totalSize = fileStream.Length;
            var totalParts = CalculatePartCount(totalSize, chunkSize);

            // 创建认证提供者
            var uploadAuth = new QiniuAuthProvider(_qiniuKey, policy.ToJson());

            // 1. 初始化分片上传
            var initResponse = await InitiateMultipartUploadAsync(bucket, key, uploadAuth, cancellationToken);
            var uploadId = initResponse.UploadId;

            if (options.EnableConsoleLog)
            {
                Console.WriteLine($"开始分片上传: UploadId={uploadId}");
                Console.WriteLine($"凭证过期时间: {DateTimeOffset.FromUnixTimeSeconds(initResponse.ExpireAt):yyyy-MM-dd HH:mm:ss}");
            }

            try
            {
                // 2. 并发上传所有分片
                var completedParts = await UploadAllPartsAsync(
                    fileStream, bucket, key, uploadId, uploadAuth, totalParts, chunkSize,
                    maxConcurrency, startTime, options.ProgressCallback, options.EnableConsoleLog, cancellationToken);

                // 3. 完成上传
                var completeRequest = new CompleteMultipartUploadRequest
                {
                    Parts = [.. completedParts.OrderBy(p => p.PartNumber)],
                    Fname = Path.GetFileName(filePath),
                    MimeType = QiniuUtils.GetMimeType(filePath)
                };

                return await CompleteMultipartUploadAsync(bucket, key, uploadId, uploadAuth, completeRequest, cancellationToken);
            }
            catch (Exception)
            {
                // 出错时清理资源
                await TryAbortMultipartUploadAsync(bucket, key, uploadId, uploadAuth, options.EnableConsoleLog);
                throw;
            }
        }

        /// <summary>
        /// 初始化分片上传
        /// 使用 Sage.Http 发送 POST 请求
        /// </summary>
        private async Task<InitiateMultipartUploadResponse> InitiateMultipartUploadAsync(
            string bucket,
            string key,
            QiniuAuthProvider authProvider,
            CancellationToken cancellationToken = default)
        {
            var encodedObjectName = string.IsNullOrEmpty(key) ? "~" : QiniuUtils.EncodeObjectName(key);
            var url = $"{_uploadHost}/buckets/{bucket}/objects/{encodedObjectName}/uploads";

            try
            {
                // 使用 Sage.Http 的流式 API
                var response = await _httpManager.CreateRequest(HttpMethod.Post, url)
                    .WithAuthentication(authProvider)
                    .WithCancellation(cancellationToken)
                    .SendAsync();

                response.EnsureSuccessStatusCode();

                var content = await response.Content.ReadAsStringAsync(cancellationToken);

                // 使用显式反序列化避免 AOT 问题
                var result = JsonSerializer.Deserialize(content, QiniuJsonSerializerContext.Default.InitiateMultipartUploadResponse);

                return result ?? throw new InvalidOperationException("初始化分片上传响应解析失败");
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"初始化分片上传失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 上传单个分片
        /// 使用 Sage.Http 的文件上传 API
        /// </summary>
        private async Task<UploadPartResponse> UploadPartAsync(
            string bucket,
            string key,
            string uploadId,
            int partNumber,
            byte[] data,
            QiniuAuthProvider authProvider,
            CancellationToken cancellationToken = default)
        {
            var encodedObjectName = string.IsNullOrEmpty(key) ? "~" : QiniuUtils.EncodeObjectName(key);
            var url = $"{_uploadHost}/buckets/{bucket}/objects/{encodedObjectName}/uploads/{uploadId}/{partNumber}";

            try
            {
                // 计算MD5用于验证
                var hash = data.ToMD5yte();
                var md5String = Convert.ToBase64String(hash);

                // 使用 Sage.Http 的文件上传 API
                using var memoryStream = new MemoryStream(data);
                var fileParam = new FileParameter(memoryStream, $"part-{partNumber}", "data", "application/octet-stream");

                var response = await _httpManager.CreateRequest(HttpMethod.Put, url)
                    .WithAuthentication(authProvider)
                    .WithHeader("Content-MD5", md5String)
                    .AddFile(fileParam)
                    .WithCancellation(cancellationToken)
                    .SendAsync();

                response.EnsureSuccessStatusCode();

                var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);

                // 使用显式反序列化避免 AOT 问题
                var result = JsonSerializer.Deserialize(responseContent, QiniuJsonSerializerContext.Default.UploadPartResponse);

                return result ?? throw new InvalidOperationException("上传分片响应解析失败");
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"上传分片{partNumber}失败: {ex.Message}", ex);
            }
        }


        /// <summary>
        /// 完成分片上传
        /// 使用 Sage.Http 发送 POST 请求
        /// </summary>
        private async Task<CompleteMultipartUploadResponse> CompleteMultipartUploadAsync(
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            CompleteMultipartUploadRequest request,
            CancellationToken cancellationToken = default)
        {
            var encodedObjectName = string.IsNullOrEmpty(key) ? "~" : QiniuUtils.EncodeObjectName(key);
            var url = $"{_uploadHost}/buckets/{bucket}/objects/{encodedObjectName}/uploads/{uploadId}";

            try
            {
                // 序列化请求体
                var jsonContent = JsonSerializer.Serialize(request, QiniuJsonSerializerContext.Default.CompleteMultipartUploadRequest);

                // 使用 Sage.Http 的流式 API 发送 JSON 请求
                var response = await _httpManager.CreateRequest(HttpMethod.Post, url)
                    .WithAuthentication(authProvider)
                    .WithBody(jsonContent, BodyType.Json)
                    .WithCancellation(cancellationToken)
                    .SendAsync();

                response.EnsureSuccessStatusCode();

                var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);

                // 使用显式反序列化避免 AOT 问题
                var result = JsonSerializer.Deserialize(responseContent, QiniuJsonSerializerContext.Default.CompleteMultipartUploadResponse);

                return result ?? throw new InvalidOperationException("完成分片上传响应解析失败");
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"完成分片上传失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 终止分片上传
        /// 使用 Sage.Http 发送 DELETE 请求
        /// </summary>
        private async Task AbortMultipartUploadAsync(
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            CancellationToken cancellationToken = default)
        {
            var encodedObjectName = string.IsNullOrEmpty(key) ? "~" : QiniuUtils.EncodeObjectName(key);
            var url = $"{_uploadHost}/buckets/{bucket}/objects/{encodedObjectName}/uploads/{uploadId}";

            try
            {
                // 使用 Sage.Http 的流式 API 发送 DELETE 请求
                var response = await _httpManager.CreateRequest(HttpMethod.Delete, url)
                    .WithAuthentication(authProvider)
                    .WithCancellation(cancellationToken)
                    .SendAsync();

                response.EnsureSuccessStatusCode();
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"终止分片上传失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 安全的终止分片上传（不抛异常）
        /// </summary>
        private async Task TryAbortMultipartUploadAsync(
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            bool enableLog = true)
        {
            try
            {
                await AbortMultipartUploadAsync(bucket, key, uploadId, authProvider);
                if (enableLog)
                    Console.WriteLine($"已清理上传任务: {uploadId}");
            }
            catch (Exception ex)
            {
                if (enableLog)
                    Console.WriteLine($"清理上传任务失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 列举已上传的分片
        /// 使用 Sage.Http 发送 GET 请求
        /// </summary>
        private async Task<ListPartsResponse> ListPartsAsync(
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            int maxParts = 1000,
            int partNumberMarker = 0,
            CancellationToken cancellationToken = default)
        {
            var encodedObjectName = string.IsNullOrEmpty(key) ? "~" : QiniuUtils.EncodeObjectName(key);
            var url = $"{_uploadHost}/buckets/{bucket}/objects/{encodedObjectName}/uploads/{uploadId}";

            try
            {
                // 构建查询参数
                var requestBuilder = _httpManager.CreateRequest(HttpMethod.Get, url)
                    .WithAuthentication(authProvider)
                    .WithCancellation(cancellationToken);

                if (maxParts != 1000)
                    requestBuilder = requestBuilder.WithQuery("max-parts", maxParts.ToString());

                if (partNumberMarker > 0)
                    requestBuilder = requestBuilder.WithQuery("part-number-marker", partNumberMarker.ToString());

                var response = await requestBuilder.SendAsync();
                response.EnsureSuccessStatusCode();

                var content = await response.Content.ReadAsStringAsync(cancellationToken);

                // 使用显式反序列化避免 AOT 问题
                var result = JsonSerializer.Deserialize(content, QiniuJsonSerializerContext.Default.ListPartsResponse);

                return result ?? throw new InvalidOperationException("列举分片响应解析失败");
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"列举已上传分片失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 并发上传所有分片
        /// </summary>
        private async Task<List<CompletePartInfo>> UploadAllPartsAsync(
            FileStream fileStream,
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            int totalParts,
            int chunkSize,
            int maxConcurrency,
            DateTime startTime,
            Action<UploadProgressEventArgs>? progressCallback,
            bool enableLog,
            CancellationToken cancellationToken)
        {
            var completedParts = new ConcurrentBag<CompletePartInfo>();
            var completedBytes = 0L;
            var totalBytes = fileStream.Length;
            var semaphore = new SemaphoreSlim(maxConcurrency);

            if (enableLog)
                Console.WriteLine($"开始上传 {totalParts} 个分片，并发数: {maxConcurrency}");

            var tasks = Enumerable.Range(1, totalParts).Select(async partNumber =>
            {
                await semaphore.WaitAsync(cancellationToken);
                try
                {
                    // 计算分片数据
                    var offset = (long)(partNumber - 1) * chunkSize;
                    var currentChunkSize = (int)Math.Min(chunkSize, totalBytes - offset);
                    var buffer = new byte[currentChunkSize];

                    // 线程安全地读取文件数据
                    lock (fileStream)
                    {
                        fileStream.Seek(offset, SeekOrigin.Begin);
                        fileStream.ReadExactly(buffer, 0, currentChunkSize);
                    }

                    // 上传分片
                    var uploadResult = await UploadPartAsync(
                        bucket, key, uploadId, partNumber, buffer, authProvider, cancellationToken);

                    // 记录完成的分片
                    completedParts.Add(new CompletePartInfo
                    {
                        PartNumber = partNumber,
                        Etag = uploadResult.Etag
                    });

                    // 更新进度
                    var newCompletedBytes = Interlocked.Add(ref completedBytes, currentChunkSize);

                    // 触发进度回调
                    var progressArgs = new UploadProgressEventArgs
                    {
                        BytesTransferred = newCompletedBytes,
                        TotalBytes = totalBytes,
                        CompletedParts = completedParts.Count,
                        TotalParts = totalParts,
                        UploadId = uploadId,
                        Key = key,
                        CurrentPartNumber = partNumber,
                        CurrentPartSize = currentChunkSize,
                        StartTime = startTime
                    };

                    progressCallback?.Invoke(progressArgs);
                    ProgressChanged?.Invoke(this, progressArgs);

                    if (enableLog)
                        Console.WriteLine($"分片 {partNumber}/{totalParts} 上传完成 ({QiniuUtils.FormatBytes(currentChunkSize)})");
                }
                catch (Exception ex)
                {
                    if (enableLog)
                        Console.WriteLine($"分片 {partNumber} 上传失败: {ex.Message}");
                    throw;
                }
                finally
                {
                    semaphore.Release();
                }
            });

            await Task.WhenAll(tasks);

            var result = completedParts.OrderBy(p => p.PartNumber).ToList();
            if (enableLog)
                Console.WriteLine($"所有分片上传完成! 共 {result.Count} 个分片");

            return result;
        }

        /// <summary>
        /// 获取所有已存在的分片（处理分页）
        /// </summary>
        private async Task<List<PartInfo>> GetAllExistingPartsAsync(
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            CancellationToken cancellationToken)
        {
            var allParts = new List<PartInfo>();
            var partNumberMarker = 0;
            const int maxParts = 1000;

            do
            {
                var response = await ListPartsAsync(
                    bucket, key, uploadId, authProvider,
                    maxParts, partNumberMarker, cancellationToken);

                allParts.AddRange(response.Parts);
                partNumberMarker = response.PartNumberMarker;

            } while (partNumberMarker > 0);

            return [.. allParts.OrderBy(p => p.PartNumber)];
        }

        /// <summary>
        /// 上传缺失的分片
        /// </summary>
        private async Task<List<CompletePartInfo>> UploadMissingPartsAsync(
            string filePath,
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            List<int> missingPartNumbers,
            int chunkSize,
            int maxConcurrency,
            DateTime startTime,
            Action<UploadProgressEventArgs>? progressCallback,
            bool enableLog,
            CancellationToken cancellationToken)
        {
            var completedParts = new ConcurrentBag<CompletePartInfo>();
            var semaphore = new SemaphoreSlim(maxConcurrency);

            using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            var totalBytes = fileStream.Length;
            var completedBytes = 0L;

            var tasks = missingPartNumbers.Select(async partNumber =>
            {
                await semaphore.WaitAsync(cancellationToken);
                try
                {
                    // 计算分片数据
                    var offset = (long)(partNumber - 1) * chunkSize;
                    var currentChunkSize = (int)Math.Min(chunkSize, totalBytes - offset);
                    var buffer = new byte[currentChunkSize];

                    // 线程安全地读取文件数据
                    lock (fileStream)
                    {
                        fileStream.Seek(offset, SeekOrigin.Begin);
                        fileStream.ReadExactly(buffer, 0, currentChunkSize);
                    }

                    // 上传分片
                    var uploadResult = await UploadPartAsync(
                        bucket, key, uploadId, partNumber, buffer, authProvider, cancellationToken);

                    // 记录完成的分片
                    completedParts.Add(new CompletePartInfo
                    {
                        PartNumber = partNumber,
                        Etag = uploadResult.Etag
                    });

                    // 更新进度
                    var newCompletedBytes = Interlocked.Add(ref completedBytes, currentChunkSize);

                    // 触发进度回调
                    progressCallback?.Invoke(new UploadProgressEventArgs
                    {
                        BytesTransferred = newCompletedBytes,
                        TotalBytes = totalBytes,
                        CompletedParts = completedParts.Count,
                        TotalParts = missingPartNumbers.Count,
                        UploadId = uploadId,
                        Key = key,
                        CurrentPartNumber = partNumber,
                        CurrentPartSize = currentChunkSize,
                        StartTime = startTime
                    });

                    if (enableLog)
                        Console.WriteLine($"缺失分片 {partNumber} 上传完成 ({QiniuUtils.FormatBytes(currentChunkSize)})");
                }
                finally
                {
                    semaphore.Release();
                }
            });

            await Task.WhenAll(tasks);
            return [.. completedParts.OrderBy(p => p.PartNumber)];
        }

        /// <summary>
        /// 自动计算分片数量
        /// </summary>
        private static int CalculatePartCount(long fileSize, int chunkSize)
        {
            if (fileSize <= 0)
                throw new ArgumentException("文件大小必须大于0", nameof(fileSize));

            var partCount = (int)Math.Ceiling((double)fileSize / chunkSize);

            if (partCount > MaxPartNumber)
            {
                throw new ArgumentException(
                    $"分片数量({partCount})超过限制({MaxPartNumber})。" +
                    $"建议增大分片大小到至少{QiniuUtils.FormatBytes(fileSize / MaxPartNumber)}");
            }

            return partCount;
        }

        /// <summary>
        /// 创建智能上传策略
        /// </summary>
        private static UploadPolicy CreateSmartUploadPolicy(string bucket, string key, string fileName, long fileSize)
        {
            var expiry = GetSmartExpiry(fileSize);
            var policy = UploadPolicy.CreateKeyPolicy(bucket, key, expiry);

            // 根据文件类型设置智能策略
            if (FileTypeUtils.IsImageFile(fileName))
            {
                ApplyImagePolicy(policy);
            }
            else if (FileTypeUtils.IsVideoFile(fileName))
            {
                ApplyVideoPolicy(policy, bucket, key);
            }
            else if (FileTypeUtils.IsAudioFile(fileName))
            {
                ApplyAudioPolicy(policy);
            }
            else if (FileTypeUtils.IsDocumentFile(fileName))
            {
                ApplyDocumentPolicy(policy);
            }
            else
            {
                ApplyGeneralPolicy(policy);
            }

            return policy;
        }

        /// <summary>
        /// 应用图片文件策略
        /// </summary>
        private static void ApplyImagePolicy(UploadPolicy policy)
        {
            policy.MimeLimit = "image/*";
            policy.FsizeLimit = 50 * 1024 * 1024; // 50MB
            policy.DetectMime = 1;
            policy.ReturnBody = """{"key":"$(key)","hash":"$(etag)","size":"$(fsize)","mimeType":"$(mimeType)","width":"$(imageInfo.width)","height":"$(imageInfo.height)","colorModel":"$(imageInfo.colorModel)","format":"$(imageInfo.format)"}""";
        }

        /// <summary>
        /// 应用视频文件策略
        /// </summary>
        private static void ApplyVideoPolicy(UploadPolicy policy, string bucket, string key)
        {
            policy.MimeLimit = "video/*";
            policy.FsizeLimit = 5L * 1024 * 1024 * 1024; // 5GB
            policy.DetectMime = 1;
            policy.ReturnBody = """{"key":"$(key)","hash":"$(etag)","size":"$(fsize)","mimeType":"$(mimeType)","duration":"$(avinfo.video.duration)","width":"$(avinfo.video.width)","height":"$(avinfo.video.height)","bitrate":"$(avinfo.video.bitrate)","fps":"$(avinfo.video.fps)"}""";

            // 自动生成视频缩略图
            var thumbKey = key.Replace(Path.GetExtension(key), "-thumb.jpg");
            policy.PersistentOps = $"vframe/jpg/offset/1/w/480/h/270|saveas/{QiniuUtils.EncodeEntryURI(bucket, thumbKey)}";
        }

        /// <summary>
        /// 应用音频文件策略
        /// </summary>
        private static void ApplyAudioPolicy(UploadPolicy policy)
        {
            policy.MimeLimit = "audio/*";
            policy.FsizeLimit = 500 * 1024 * 1024; // 500MB
            policy.DetectMime = 1;
            policy.ReturnBody = """{"key":"$(key)","hash":"$(etag)","size":"$(fsize)","mimeType":"$(mimeType)","duration":"$(avinfo.audio.duration)","bitrate":"$(avinfo.audio.bitrate)","sampleRate":"$(avinfo.audio.samplerate)"}""";
        }

        /// <summary>
        /// 应用文档文件策略
        /// </summary>
        private static void ApplyDocumentPolicy(UploadPolicy policy)
        {
            policy.MimeLimit = "application/pdf;application/msword;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;text/plain";
            policy.FsizeLimit = 100 * 1024 * 1024; // 100MB
            policy.DetectMime = 1;
            policy.ReturnBody = """{"key":"$(key)","hash":"$(etag)","size":"$(fsize)","mimeType":"$(mimeType)","uploadTime":"$(timestamp)"}""";
        }

        /// <summary>
        /// 应用通用文件策略
        /// </summary>
        private static void ApplyGeneralPolicy(UploadPolicy policy)
        {
            policy.FsizeLimit = 2L * 1024 * 1024 * 1024; // 2GB
            policy.DetectMime = 1;
            policy.ReturnBody = """{"key":"$(key)","hash":"$(etag)","size":"$(fsize)","mimeType":"$(mimeType)"}""";
        }

        /// <summary>
        /// 根据文件大小智能确定有效期
        /// </summary>
        private static int GetSmartExpiry(long fileSize)
        {
            return fileSize switch
            {
                < 100 * 1024 * 1024 => 3600,        // <100MB: 1小时
                < 1024L * 1024 * 1024 => 7200,      // <1GB: 2小时
                _ => 14400                           // >=1GB: 4小时
            };
        }

        #endregion

        #region 高级上传方法（带文件流和进度支持）

        /// <summary>
        /// 高级分片上传（支持自定义文件流和详细进度）
        /// </summary>
        /// <param name="fileStream">文件流</param>
        /// <param name="fileName">文件名</param>
        /// <param name="bucket">存储桶</param>
        /// <param name="key">文件键</param>
        /// <param name="uploadPolicy">上传策略</param>
        /// <param name="options">上传选项</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>上传结果</returns>
        public async Task<CompleteMultipartUploadResponse> UploadStreamAsync(
            Stream fileStream,
            string fileName,
            string bucket,
            string key,
            UploadPolicy? uploadPolicy = null,
            UploadOptions? options = null,
            CancellationToken cancellationToken = default)
        {
            ArgumentNullException.ThrowIfNull(fileStream, nameof(fileStream));
            ArgumentException.ThrowIfNullOrEmpty(fileName, nameof(fileName));
            ArgumentException.ThrowIfNullOrEmpty(bucket, nameof(bucket));
            ArgumentException.ThrowIfNullOrEmpty(key, nameof(key));

            if (!fileStream.CanRead)
                throw new ArgumentException("文件流必须可读", nameof(fileStream));

            var fileSize = fileStream.Length;
            var startTime = DateTime.Now;

            options ??= new UploadOptions();
            var chunkSize = options.ChunkSize ?? QiniuUtils.GetRecommendedChunkSize(fileSize);
            var maxConcurrency = Math.Max(options.MaxConcurrency, 1);
            var totalParts = CalculatePartCount(fileSize, chunkSize);

            // 创建或使用提供的上传策略
            var effectivePolicy = uploadPolicy ?? CreateSmartUploadPolicy(bucket, key, fileName, fileSize);
            var uploadAuth = new QiniuAuthProvider(_qiniuKey, effectivePolicy.ToJson());

            try
            {
                // 1. 初始化分片上传
                var initResponse = await InitiateMultipartUploadAsync(bucket, key, uploadAuth, cancellationToken);
                var uploadId = initResponse.UploadId;

                if (options.EnableConsoleLog)
                    Console.WriteLine($"🔄 开始流式分片上传: UploadId={uploadId}");

                // 2. 上传所有分片
                var completedParts = await UploadStreamPartsAsync(
                    fileStream, bucket, key, uploadId, uploadAuth,
                    totalParts, chunkSize, maxConcurrency, startTime,
                    options.ProgressCallback, options.EnableConsoleLog, cancellationToken);

                // 3. 完成上传
                var completeRequest = new CompleteMultipartUploadRequest
                {
                    Parts = [.. completedParts.OrderBy(p => p.PartNumber)],
                    Fname = fileName,
                    MimeType = QiniuUtils.GetMimeType(fileName)
                };

                var result = await CompleteMultipartUploadAsync(bucket, key, uploadId, uploadAuth, completeRequest, cancellationToken);

                var duration = DateTime.Now - startTime;
                if (options.EnableConsoleLog)
                    Console.WriteLine($"流式上传完成: {result.Key} (耗时: {duration.TotalSeconds:F1}秒)");

                return result;
            }
            catch (Exception ex)
            {
                if (options.EnableConsoleLog)
                    Console.WriteLine($"流式上传失败: {ex.Message}");
                throw;
            }
        }

        /// <summary>
        /// 上传流的所有分片
        /// </summary>
        private async Task<List<CompletePartInfo>> UploadStreamPartsAsync(
            Stream fileStream,
            string bucket,
            string key,
            string uploadId,
            QiniuAuthProvider authProvider,
            int totalParts,
            int chunkSize,
            int maxConcurrency,
            DateTime startTime,
            Action<UploadProgressEventArgs>? progressCallback,
            bool enableLog,
            CancellationToken cancellationToken)
        {
            var completedParts = new ConcurrentBag<CompletePartInfo>();
            var completedBytes = 0L;
            var totalBytes = fileStream.Length;
            var semaphore = new SemaphoreSlim(maxConcurrency);

            if (enableLog)
                Console.WriteLine($"开始上传 {totalParts} 个分片，并发数: {maxConcurrency}");

            var tasks = Enumerable.Range(1, totalParts).Select(async partNumber =>
            {
                await semaphore.WaitAsync(cancellationToken);
                try
                {
                    // 计算分片数据
                    var offset = (long)(partNumber - 1) * chunkSize;
                    var currentChunkSize = (int)Math.Min(chunkSize, totalBytes - offset);
                    var buffer = new byte[currentChunkSize];

                    // 线程安全地读取流数据
                    lock (fileStream)
                    {
                        fileStream.Seek(offset, SeekOrigin.Begin);
                        fileStream.ReadExactly(buffer, 0, currentChunkSize);
                    }

                    // 上传分片
                    var uploadResult = await UploadPartAsync(
                        bucket, key, uploadId, partNumber, buffer, authProvider, cancellationToken);

                    // 记录完成的分片
                    completedParts.Add(new CompletePartInfo
                    {
                        PartNumber = partNumber,
                        Etag = uploadResult.Etag
                    });

                    // 更新进度
                    var newCompletedBytes = Interlocked.Add(ref completedBytes, currentChunkSize);

                    // 触发进度回调
                    var progressArgs = new UploadProgressEventArgs
                    {
                        BytesTransferred = newCompletedBytes,
                        TotalBytes = totalBytes,
                        CompletedParts = completedParts.Count,
                        TotalParts = totalParts,
                        UploadId = uploadId,
                        Key = key,
                        CurrentPartNumber = partNumber,
                        CurrentPartSize = currentChunkSize,
                        StartTime = startTime
                    };

                    progressCallback?.Invoke(progressArgs);
                    ProgressChanged?.Invoke(this, progressArgs);

                    if (enableLog)
                        Console.WriteLine($"分片 {partNumber}/{totalParts} 上传完成 ({QiniuUtils.FormatBytes(currentChunkSize)})");
                }
                finally
                {
                    semaphore.Release();
                }
            });

            await Task.WhenAll(tasks);
            return [.. completedParts.OrderBy(p => p.PartNumber)];
        }

        #endregion

        #region 便捷方法

        /// <summary>
        /// 快速上传单个文件（最简化接口）
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <param name="bucket">存储桶</param>
        /// <param name="key">文件键</param>
        /// <param name="progressCallback">进度回调</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>上传结果</returns>
        public async Task<CompleteMultipartUploadResponse> QuickUploadAsync(
            string filePath,
            string bucket,
            string key,
            Action<UploadProgressEventArgs>? progressCallback = null,
            CancellationToken cancellationToken = default)
        {
            var options = new UploadOptions
            {
                ProgressCallback = progressCallback,
                EnableConsoleLog = progressCallback == null // 有回调时不输出日志，避免冗余
            };

            return await UploadFileAsync(filePath, bucket, key, null, options, cancellationToken);
        }

        /// <summary>
        /// 快速批量上传（最简化接口）
        /// </summary>
        /// <param name="filePaths">文件路径列表</param>
        /// <param name="bucket">存储桶</param>
        /// <param name="keyPrefix">键名前缀（可选）</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>批量上传结果</returns>
        public async Task<List<BatchUploadResult>> QuickBatchUploadAsync(
            IEnumerable<string> filePaths,
            string bucket,
            string keyPrefix = "",
            CancellationToken cancellationToken = default)
        {
            return await UploadBatchAsync(
                filePaths,
                bucket,
                filePath =>
                {
                    var fileName = Path.GetFileName(filePath);
                    return string.IsNullOrEmpty(keyPrefix) ? fileName : $"{keyPrefix.TrimEnd('/')}/{fileName}";
                },
                null,
                new UploadOptions { BatchConcurrency = 2, MaxConcurrency = 2 },
                cancellationToken);
        }

        /// <summary>
        /// 带进度监控的文件上传
        /// 使用 Sage.Http 的进度报告功能
        /// </summary>
        public async Task<CompleteMultipartUploadResponse> UploadWithProgressAsync(
            string filePath,
            string bucket,
            string key,
            IProgress<float> uploadProgress,
            UploadPolicy? uploadPolicy = null,
            UploadOptions? options = null,
            CancellationToken cancellationToken = default)
        {
            var progressCallback = new Action<UploadProgressEventArgs>(args =>
            {
                // 将内部进度转换为 Sage.Http 的进度格式
                uploadProgress?.Report((float)args.ProgressPercentage / 100f);
            });

            var uploadOptions = options ?? new UploadOptions();
            uploadOptions.ProgressCallback = progressCallback;

            return await UploadFileAsync(filePath, bucket, key, uploadPolicy, uploadOptions, cancellationToken);
        }

        #endregion
    }

}
